/// 
/// MaDAson - Toolkit for Model Driven Software Development
/// 
/// Copyright [2006] Marcus Lankenau
/// 
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///  
///     http://www.apache.org/licenses/LICENSE-2.0
///  
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
/// 

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Serialization;


namespace Madason.MetaModel.Interfaces
{
	#region classes for deserialization of staruml5 fileformat

	[XmlRoot(ElementName = "PROJECT", Namespace = "http://www.staruml.com")] 
	[Serializable]
	public class StarUmlModel
	{
		private StarUmlBody body;

		[XmlElement(ElementName = "BODY", Namespace = "http://www.staruml.com")]
		public StarUmlBody Body
		{
			get { return body; }
			set { body = value; }
		}
			
			 
	}

	[Serializable]
	public class StarUmlBody
	{
		private List<StarUmlObject> objects;

		[XmlElement(ElementName = "OBJ", Namespace = "http://www.staruml.com")]
		public List<StarUmlObject> Objects
		{
			get { return objects; }
			set { objects = value; }
		}
	}

	[Serializable]
	public class StarUmlObject
	{		
		private string name;
		private string type;
		private string guid;
		private List<StarUmlAttribute> attributes = new List<StarUmlAttribute>();
		private List<StarUmlReference> references = new List<StarUmlReference>();
		private List<StarUmlObject> objects =  new List<StarUmlObject>();
		private StarUmlObject parent = null;
		
		[XmlIgnore]
		public StarUmlObject Parent
		{
			get { return parent; }
			set { parent = value; }
		}
		
		[XmlAttribute(AttributeName="name")]
		public string Name
		{
			get { return name; }
			set { name = value; }
		}

		[XmlAttribute(AttributeName = "type")]
		public string Type
		{
			get { return type; }
			set { type = value; }
		}

		[XmlAttribute(AttributeName = "guid")]
		public string Guid
		{
			get { return guid; }
			set { guid = value; }
		}

		[XmlElement(ElementName = "OBJ", Namespace = "http://www.staruml.com")]
		public List<StarUmlObject> Objects
		{
			get { return objects; }
			set { objects = value; }
		}	 


		[XmlElement(ElementName = "ATTR", Namespace = "http://www.staruml.com")]
		public List<StarUmlAttribute> Attributes
		{
			get { return attributes; }
			set { attributes = value; }
		}

		[XmlElement(ElementName = "REF", Namespace = "http://www.staruml.com")]
		public List<StarUmlReference> References
		{
			get { return references; }
			set { references = value; }
		}

		public StarUmlObject()
		{
		}

		public StarUmlObject(string name, string type, string guid)
		{
			this.name = name;
			this.type = type;
			this.guid = guid;
		}

		public string this[string key]
		{
			get
			{
				foreach (StarUmlAttribute attr in Attributes)
					if (attr.Name == key)
						return attr.Value;
				foreach (StarUmlReference refr in References)
					if (refr.Name == key)
						return refr.Value;
				return null;
			}
		}
		
		public void SetParentRec(StarUmlObject parent)
		{
			this.parent = parent;
			foreach (StarUmlObject o in objects)
				o.SetParentRec(this);
		}
	}

	[Serializable]
	public class StarUmlAttribute
	{
		private string name;
		private string type;
		private string _value;

		[XmlAttribute(AttributeName = "name")]
		public string Name
		{
			get { return name; }
			set { name = value; }
		}

		[XmlAttribute(AttributeName = "type")]
		public string Type
		{
			get { return type; }
			set { type = value; }
		}

		[XmlTextAttribute]
		public string Value
		{
			get { return _value; }
			set { _value = value; }
		}

		public StarUmlAttribute()
		{
		}

		public StarUmlAttribute(string name, string type, string value)
		{
			this.name = name;
			this.type = type;
			this._value = value;
		}
	}


	[Serializable]
	public class StarUmlReference
	{
		private string name;
		private string type;
		private string _value;

		[XmlAttribute(AttributeName = "name")]
		public string Name
		{
			get { return name; }
			set { name = value; }
		}

		[XmlText]
		public string Value
		{
			get { return _value; }
			set { _value = value; }
		}

		public StarUmlReference()
		{
		}


		public StarUmlReference(string name, string value)
		{
			this.name = name;
			this._value = value;
		}
	}


	#endregion



	public class ImportStarUml5 : IMetaModelImport
	{
		const string OBJ_TYPE_PACKAGE = "UMLPackage";
		const string OBJ_TYPE_CLASS = "UMLClass";
		const string OBJ_TYPE_GENERALIZATION = "UMLGeneralization";
		const string OBJ_TYPE_TAGGEDVALUE = "TaggedValue";
		const string OBJ_TYPE_ATTRIBUTE = "UMLAttribute";
		const string OBJ_TYPE_OPERATION = "UMLOperation";
		const string OBJ_TYPE_PARAMETER = "UMLParameter";
		const string OBJ_TYPE_ASSOCIATION = "UMLAssociation";
		const string OBJ_TYPE_ASSOCIATION_END = "UMLAssociationEnd";		
		const string OBJ_TYPE_STATEMACHINE = "UMLStateMachine";
		const string OBJ_TYPE_COMPOSITE_STATE = "UMLCompositeState";
		const string OBJ_TYPE_PSEUDO_STATE = "UMLPseudostate";
		const string OBJ_TYPE_UNINTERPRETED_ACTION = "UMLUninterpretedAction";
		const string OBJ_TYPE_FINAL_STATE = "UMLFinalState";
		const string OBJ_TYPE_TRANSITION = "UMLTransition";
		const string OBJ_TYPE_CALL_EVENT = "UMLCallEvent";
		const string OBJ_TYPE_DEPENDENCY = "UMLDependency";
        const string OBJ_TYPE_ACTIVITY_GRAPH = "UMLActivityGraph";
        const string OBJ_TYPE_ACTIONSTATE = "UMLActionState";

		Model model = new Model();
		Namespace currentNamespace;
		Class currentClass;

		ModelElement currentModelElement;
		Dictionary<string, ModelElement> modelElementMap = new Dictionary<string, ModelElement>();
		Stack<ModelElement> stack = new Stack<ModelElement>();
		//added by hefeisla
		/// <summary>
		/// this stack collects all Elements, e.g. Associations, that cannot be deserialized
		/// at the actual time in XML-Tree because they references a class that ist located in
		/// another package which is was not deserialized at the actual time. At the end of the
		/// deserialization the elements of this stack will be handled again.
		/// </summary>
		Stack<StarUmlObject> queuedElements = new Stack<StarUmlObject>();

		protected string GetTypeForAttrOrParam(StarUmlObject obj, Namespace ns)
		{
			if (obj["Type_"] != null)
			{
				return obj["Type_"];
			}
			else if (obj["TypeExpression"] != null)
			{
				string typeExpr = obj["TypeExpression"];

				if (ns.FindClassByName(typeExpr) != null)
				{
					return ns.FindClassByName(typeExpr).Id;
				}
				else if (ns.Model.FindClassByName(typeExpr) != null)
				{
					return ns.Model.FindClassByName(typeExpr).Id;
				}
				else
				{
					model.FindClassByName(typeExpr);
					Class attrType = new Class();
					attrType.Namespace = model;
					model.Classes.Add(attrType);
					attrType.Name = typeExpr;
					attrType.Id = Guid.NewGuid().ToString();
					return attrType.Id;
				}
			}
			else if (obj.Parent["StereotypeName"].Equals("Enum"))
			{
				return "";
			}
			throw new ApplicationException("Can't get Type for "+obj.Name+",  " +obj.Guid);
			
		}

		/// <summary>
		/// Handle a object in the object tree recursivly
		/// </summary>
		/// <param name="obj"></param>
		protected void HandleObject(StarUmlObject obj)
		{
			bool elementUsed = false;

			// handle object opening
			if (obj.Type == OBJ_TYPE_PACKAGE)
			{
				stack.Push(currentModelElement);
				elementUsed = true;
				Namespace ns = new Namespace();
				ns.Name = obj["Name"];
				ns.Id = obj.Guid;
				ns.Parent = currentNamespace;
				ns.StereotypeName = obj["StereotypeName"];
				currentNamespace.Namespaces.Add(ns);
				currentNamespace = ns;
				currentModelElement = ns;
			}
			else if (obj.Type == OBJ_TYPE_CLASS)
			{
				stack.Push(currentModelElement);
				elementUsed = true;
				Class cls = new Class();
				cls.Namespace = currentNamespace;
				currentNamespace.Classes.Add(cls);
				cls.Id = obj.Guid;
				cls.Name = obj["Name"];
				cls.StereotypeName = obj["StereotypeName"];
				currentModelElement = cls;
				currentClass = cls;
				modelElementMap[cls.Id] = cls;
			}
			else if (obj.Type == OBJ_TYPE_DEPENDENCY)
			{
				stack.Push(currentModelElement);
				elementUsed = true;

				Dependency dep = new Dependency(model);
				dep.SourceId = obj["Client"];
				dep.TargetId = obj["Supplier"];
				dep.Source.Dependencies.Add(dep);				
				currentModelElement = dep;
			}
			else if (obj.Type == OBJ_TYPE_GENERALIZATION)
			{
				Class cls = model.FindClassById(obj["Child"]);
				cls.SuperClassID = obj["Parent"];
			}
			else if (obj.Type == OBJ_TYPE_TAGGEDVALUE)
			{
				stack.Push(currentModelElement);
				elementUsed = true;
				TaggedValue tv = new TaggedValue();
				tv.Key = obj["Name"];
				tv.Value = obj["DataValue"];

				if (currentModelElement == null)
					throw new Exception("can't place tagged value " + obj.Guid);

				currentModelElement.TaggedValues.Add(tv);
			}
			else if (obj.Type == OBJ_TYPE_ATTRIBUTE)
			{
				stack.Push(currentModelElement);
				elementUsed = true;
				Attribute attr = new Attribute();
				currentClass.Attributes.Add(attr);
				attr.Class = currentClass;
				attr.Name = obj["Name"];
				attr.Id = obj.Guid;
				//added by hefeisla
				if (obj["Multiplicity"] != null)
					attr.Multiplicity = obj["Multiplicity"];
				if (obj["StereotypeName"] != null)
					attr.StereotypeName = obj["StereotypeName"];
				if (obj["InitialValue"] != null)
					attr.InitialValue = obj["InitialValue"];
				attr.TypeId = GetTypeForAttrOrParam(obj, currentClass.Namespace);
				currentModelElement = attr;
			}
			else if (obj.Type == OBJ_TYPE_OPERATION)
			{
				stack.Push(currentModelElement);
				elementUsed = true;
				Operation op = new Operation();
				currentClass.Operations.Add(op);
				op.Class = currentClass;
				op.Name = obj["Name"];
				op.Id = obj.Guid;
				if (obj["StereotypeName"] != null) op.StereotypeName = obj["StereotypeName"];
				currentModelElement = op;
			}
			else if (obj.Type == OBJ_TYPE_PARAMETER)
			{
				stack.Push(currentModelElement);
				elementUsed = true;
				Operation op = currentModelElement as Operation;
				Parameter param = new Parameter();
				param.Operation = op;
				param.Name = obj["Name"];
				param.TypeId = GetTypeForAttrOrParam(obj, op.Class.Namespace);
				if (obj["DirectionKind"] != "pdkReturn")
				{
					if (obj["DirectionKind"] == "pdkOut")
						param.Dir = ParameterDir.Out;
					else if (obj["DirectionKind"] == "pdkInOut")
						param.Dir = ParameterDir.InOut;
 
					op.Parameters.Add(param);
				}
				else
					op.ReturnTypeId = param.TypeId;

				currentModelElement = param;
			}
			else if (obj.Type == OBJ_TYPE_ASSOCIATION_END)
			{
				stack.Push(currentModelElement);
				elementUsed = true;

				StarUmlObject otherEnd = obj.Parent.Objects[0] == obj ? obj.Parent.Objects[1] : obj.Parent.Objects[0];

				try
				{
					Class source = modelElementMap[otherEnd["Participant"]] as Class;
					Class dest = modelElementMap[obj["Participant"]] as Class;


					Association assoc = new Association();
					assoc.Id = obj.Guid;
					assoc.Class = source;
					assoc.TargetClassId = dest.Id;
					assoc.Name = obj["Name"];
					if (assoc.Name == null) assoc.Name = "";
					assoc.OtherEndID = otherEnd.Guid;
					assoc.CardinalityText = obj["Multiplicity"];
					//added by hefeisla
					if (obj["IsNavigable"] != null && obj["IsNavigable"].Equals("False"))
					{
						assoc.IsNavigable = false;
					}
					if (assoc.CardinalityText == null)
					{
						assoc.CardinalityText = "1";
					}
					source.Associations.Add(assoc);
					//end added
					currentModelElement = assoc;
				}
				catch (System.Collections.Generic.KeyNotFoundException e)
				{
					this.queuedElements.Push(obj);
				}
			}

			else if (obj.Type == OBJ_TYPE_STATEMACHINE)
			{
				stack.Push(currentModelElement);
				elementUsed = true;
				StateMachine sm = new StateMachine();
				if (obj["StereotypeName"] != null) sm.StereotypeName = obj["StereotypeName"];

				if (currentModelElement is Class)
				{
					Class cls = currentModelElement as Class;
					cls.StateMachines.Add(sm);
					sm.Class = cls;
				}
				else if (currentModelElement is Namespace)
				{
					Namespace ns = currentModelElement as Namespace;
					ns.Statemachines.Add(sm);
				}
				sm.Name = obj["Name"];
				currentModelElement = sm;
			}
            else if (obj.Type == OBJ_TYPE_ACTIVITY_GRAPH)
            {
                StateMachine statemachine = new StateMachine();
                statemachine.Id = obj.Guid;
                statemachine.Name = obj.Name;
                
                if (obj["StereotypeName"] != null) statemachine.StereotypeName = obj["StereotypeName"];
                if (currentModelElement is Namespace)
                    (currentModelElement as Namespace).Statemachines.Add(statemachine);
                else if (currentModelElement is Class)
                    (currentModelElement as Class).StateMachines.Add(statemachine);
                currentModelElement = statemachine;
                modelElementMap[statemachine.Id] = statemachine;
            }
            else if (obj.Type == OBJ_TYPE_ACTIONSTATE || obj.Type == OBJ_TYPE_COMPOSITE_STATE || obj.Type == OBJ_TYPE_PSEUDO_STATE || obj.Type == OBJ_TYPE_FINAL_STATE)
            {
                if (obj.Name != "Top")
                {
                    stack.Push(currentModelElement);
                    elementUsed = true;
                    State state = new State();
                    state.Class = currentClass;
                    state.Parent = currentModelElement as State;
                    state.Name = obj["Name"];
                    state.Id = obj.Guid;
                    if (obj["StereotypeName"] != null) state.StereotypeName = obj["StereotypeName"];
                    if (obj["PseudostateKind"] == "pkInitial") state.StateType = StateType.Initial;
                    else if (obj["PseudostateKind"] == "pkChoice") state.StateType = StateType.Branch;
                    else if (obj.Type == OBJ_TYPE_FINAL_STATE) state.StateType = StateType.Final;
                    else state.StateType = StateType.Simple;
                    (currentModelElement as State).States.Add(state);
                    currentModelElement = state;
                    modelElementMap[state.Id] = state;
                }
            }
            else if (obj.Type == OBJ_TYPE_TRANSITION)
            {
                stack.Push(currentModelElement);
                elementUsed = true;

                State source = modelElementMap[obj["Source"]] as State;
                StateTransition transition = new StateTransition();
                transition.Name = obj["Name"];
                transition.Target = modelElementMap[obj["Target"]] as State;
                transition.Source = source;
                transition.Guard = obj["GuardCondition"];
                source.Transitions.Add(transition);
                currentModelElement = transition;
            }
            else if (obj.Type == OBJ_TYPE_CALL_EVENT)
            {
                StateTransitionTrigger trigger = new StateTransitionTrigger();
                trigger.Id = obj.Guid;
                trigger.Name = obj["Name"];
                if (currentModelElement is StateTransition)
                    (currentModelElement as StateTransition).Trigger.Add(trigger);
            }
            else if (obj.Type == OBJ_TYPE_UNINTERPRETED_ACTION)
            {
                StateAction action = new StateAction();
                action.Name = obj["Name"];
                action.Id = obj.Guid;
                State state = currentModelElement as State;
                if (state != null)
                {
                    if (obj.Name.StartsWith("EntryActions"))
                        state.EntryActions.Add(action);
                    if (obj.Name.StartsWith("ExitActions"))
                        state.ExitActions.Add(action);
                }
                StateTransition trans = currentModelElement as StateTransition;
                if (trans != null)
                {
                    trans.Actions.Add(action);
                }
            }

			// care about child-objects
			foreach (StarUmlObject childobj in obj.Objects)
				HandleObject(childobj);


			if (obj.Type == OBJ_TYPE_PACKAGE)
			{
				currentNamespace = currentNamespace.Parent;
			}

			
			if (elementUsed)
				currentModelElement = stack.Pop();
		}

		/// <summary>
		/// Import Staruml 5 Model Files
		/// </summary>
		/// <param name="reader"></param>
		/// <returns></returns>
		public Model Import(System.IO.TextReader reader)
		{
			XmlSerializer serializer = new XmlSerializer(typeof(StarUmlModel));
			StarUmlModel suModel = serializer.Deserialize(reader) as StarUmlModel;

			currentNamespace = model;

			foreach (StarUmlObject obj in suModel.Body.Objects)
				obj.SetParentRec(null);				
				
			foreach (StarUmlObject obj in suModel.Body.Objects)
				HandleObject(obj);

			while (queuedElements.Count > 0)
			{
				HandleObject(queuedElements.Pop());
			}

			return model;
		}

		
	}
}
